﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Xml.Linq;
using System.Xml;
using System.Xml.Xsl;
using System.Globalization;

namespace LotroData
{
    /// <summary>
    /// Provides access to the data.lotro.com XML streams.
    /// </summary>
    public sealed class LotroXmlRequest
    {
        /// <summary>
        /// Occurs when a XML request is started.
        /// </summary>
        public event EventHandler<LotroEventArgs> XmlRequestStarted;

        /// <summary>
        /// Called when a XML request is started.
        /// </summary>
        /// <param name="sender">The sender object.</param>
        /// <param name="e">The <see cref="LotroData.LotroEventArgs"/> instance containing the event data.</param>
        private void OnXmlRequestStarted(object sender, LotroEventArgs e)
        {
            var temp = XmlRequestStarted;
            if (temp != null)
                temp(sender, e);
        }

        /// <summary>
        /// Occurs when a XML request has completed.
        /// </summary>
        public event EventHandler<LotroEventArgs> XmlRequestCompleted;

        /// <summary>
        /// Called when a XML request has completed.
        /// </summary>
        /// <param name="sender">The sender object.</param>
        /// <param name="e">The <see cref="LotroData.LotroEventArgs"/> instance containing the event data.</param>
        private void OnXmlRequestCompleted(object sender, LotroEventArgs e)
        {
            var temp = XmlRequestCompleted;
            if (temp != null)
                temp(sender, e);
        }

        private const string BaseAddress = "http://data.lotro.com/";
        private LotroCredential Credentials;

        /// <summary>
        /// Initializes a new instance of the <see cref="LotroXmlRequest"/> class.
        /// </summary>
        /// <param name="credentials">The credentials with which to connect to data.lotro.com.</param>
        public LotroXmlRequest(LotroCredential credentials)
        {
            if (credentials == null) throw new ArgumentNullException("credentials");

            this.Credentials = credentials;
        }

        /// <summary>
        /// Gets the XML character sheet.
        /// </summary>
        /// <param name="characterName">The name of the character.</param>
        /// <param name="world">The world where the character resides.</param>
        /// <returns></returns>
        public XElement GetCharacterSheet(string characterName, LotroWorld world)
        {
            if (string.IsNullOrEmpty(characterName)) throw new ArgumentNullException("characterName");
            if (world == LotroWorld.Unknown) throw new ArgumentException("Specified world cannot be Unknown", "world");

            return LoadDataXml(LotroXmlRequestType.CharacterSheet, characterName, world, -1);
        }

        /// <summary>
        /// Gets the XML guild roster.
        /// </summary>
        /// <param name="guildName">The name of the guild.</param>
        /// <param name="world">The world where the guild resides.</param>
        /// <returns></returns>
        public XElement GetGuildRoster(string guildName, LotroWorld world)
        {
            if (string.IsNullOrEmpty(guildName)) throw new ArgumentNullException("guildName");
            if (world == LotroWorld.Unknown) throw new ArgumentException("Specified world cannot be Unknown", "world");

            return LoadDataXml(LotroXmlRequestType.GuildRoster, guildName, world, -1);
        }

        /// <summary>
        /// Gets the XML item sheet.
        /// </summary>
        /// <param name="itemId">The item id.</param>
        /// <returns></returns>
        public XElement GetItemSheet(int itemId)
        {
            if (itemId <= 0) throw new ArgumentOutOfRangeException("itemId");

            return LoadDataXml(LotroXmlRequestType.Item, string.Empty, LotroWorld.Unknown, itemId);
        }

        /// <summary>
        /// Downloads the XML stream.
        /// </summary>
        /// <param name="address">The address to download from.</param>
        /// <returns></returns>
        private static Stream DownloadXmlStream(Uri address)
        {
            try
            {
                var request = WebRequest.Create(address);

                var response = request.GetResponse();

                return response.GetResponseStream();
            }
            catch (WebException)
            {
                throw;
            }
        }

        private XElement LoadDataXml(LotroXmlRequestType type, string name, LotroWorld world, int itemId)
        {
            OnXmlRequestStarted(this, new LotroEventArgs(type, name, world, itemId));

            XElement xElement = null;

            //Check cache for cached version
            switch (type)
            {
                case LotroXmlRequestType.CharacterSheet:
                case LotroXmlRequestType.GuildRoster:
                    xElement = LotroCache.LoadXmlFromCache(name, world, true);
                    break;
                case LotroXmlRequestType.Item:
                    xElement = LotroCache.LoadXmlFromCache(itemId, true);
                    break;
                default:
                    break;
            }

            //If cached version exists, use it!
            if (xElement != null)
                return xElement;

            //Build the data address.
            var address = BuildDataAddress(type, name, world, itemId);

            //Download the XML Stream
            try
            {
                var reader = XmlReader.Create(DownloadXmlStream(address));
                reader.MoveToContent();

                //Load the XElement
                xElement = XElement.Load(reader);
            }
            catch (XmlException ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);

                //If there is an error when download or loading, attempt to load a cached version
                switch (type)
                {
                    case LotroXmlRequestType.CharacterSheet:
                    case LotroXmlRequestType.GuildRoster:
                        xElement = LotroCache.LoadXmlFromCache(name, world, false);
                        break;
                    case LotroXmlRequestType.Item:
                        xElement = LotroCache.LoadXmlFromCache(itemId, false);
                        break;
                    default:
                        break;
                }

                if (xElement != null)
                    return xElement;
                else
                    throw new LotroException("XML could not be downloaded, or loaded from the cache", ex);
            }

            //Check for invalid XML
            var errorE = xElement.Element("error");
            //If it's not null, there is an error
            if (errorE != null)
            {
                var message = errorE.Attribute("message").Value;
                var code = errorE.Attribute("code").Value;

                throw new LotroException(message, code);
            }

            //Save the cache file
            switch (type)
            {
                case LotroXmlRequestType.CharacterSheet:
                case LotroXmlRequestType.GuildRoster:
                    //Deletes the old cache file
                    LotroCache.DeleteFromCache(name, world);
                    //Saves the new one
                    LotroCache.SaveXmlToCache(name, world, xElement);
                    break;
                case LotroXmlRequestType.Item:
                    //Deletes the old cache file
                    LotroCache.DeleteFromCache(itemId);
                    //Saves the new one
                    LotroCache.SaveXmlToCache(itemId, xElement);
                    break;
                default:
                    break;
            }

            OnXmlRequestCompleted(this, new LotroEventArgs(type, name, world, itemId));

            return xElement;
        }

        private Uri BuildDataAddress(LotroXmlRequestType type, string name, LotroWorld world, int id)
        {
            var builder = BuildAddressStart(type);

            switch (type)
            {
                case LotroXmlRequestType.CharacterSheet:
                case LotroXmlRequestType.GuildRoster:
                    builder.Append("w/"); //World
                    builder.Append(world.ToString());
                    builder.Append("/");
                    builder.Append(type.ToString().ToLower(CultureInfo.CurrentCulture)[0]); //Guild or Character (g or c)
                    builder.Append("/");
                    builder.Append(name);
                    builder.Append("/");
                    break;
                case LotroXmlRequestType.Item:
                    builder.Append("id/");
                    builder.Append(id);
                    builder.Append("/");
                    break;
                default:
                    throw new LotroException("Data Address build expection");
            }


            return new Uri(builder.ToString());
        }

        private StringBuilder BuildAddressStart(LotroXmlRequestType type)
        {
            var builder = new StringBuilder();

            builder.Append(BaseAddress);

            var devName = Credentials.IsEncrypted ? Credentials.DeveloperName.Decrypt() : Credentials.DeveloperName;
            builder.Append(devName);
            builder.Append("/");

            var apiKey = Credentials.IsEncrypted ? Credentials.ApiKey.Decrypt() : Credentials.ApiKey;
            builder.Append(apiKey);
            builder.Append("/");
            builder.Append(type.ToString().ToLower(CultureInfo.CurrentCulture));
            builder.Append("/");

            return builder;
        }
    }
}
